<template>
  <view class="flex flex-row flex-row-bottom-center">
    <slot name="prefix">
      <tm-text
        :followTheme="props.followTheme"
        :color="props.color"
        :font-size="props.fontSize * 0.7"
        :label="props.prefix"
      ></tm-text>
    </slot>
    <slot name="default">
      <tm-text
        :followTheme="props.followTheme"
        _class="px-12"
        :color="props.color"
        :font-size="props.fontSize"
        :label="displayValue"
      ></tm-text>
    </slot>
    <slot name="suffix">
      <tm-text
        :followTheme="props.followTheme"
        :color="props.color"
        :font-size="props.fontSize * 0.7"
        :label="props.suffix"
      ></tm-text>
    </slot>
  </view>
</template>

<script lang="ts" setup>
/**
 * 数值
 * @description 主要用来显示数值，翻转动画。
 * @property {Number} startVal = [] 0,起始值
 * @property {Number} endVal = [] 0,最终值
 * @property {Number} duration = [] 3000,从起始值到结束值数字变动的时间
 * @property {Boolean} autoplay = [] true,是否自动播放
 * @property {Number} decimals = [] 0,保留的小数位数
 * @property {String} decimal = [] '.',小数点分割符号
 * @property {String} separator = [] ',',上了三位数分割的符号
 * @property {String} prefix = [] '',前缀
 * @property {String} suffix = [] '',后缀
 * @property {Boolean} isFrequent = [] false,是否隔一段时间数字跳动，这里的跳动是隔一段时间设置初始值
 * @property {Number} frequentTime = [] 5000,跳动间隔时间
 * @emits [mountedCallback,callback] 事件
 * @template prefix 前缀插槽,default数值内容插槽,suffix后缀插槽。
 * @example <tm-statistic :font-size="80" prefix="￥" suffix="元"></tm-statistic>
 */
import { requestAnimationFrame, cancelAnimationFrame } from "./requestAnimationFrame";
import { computed, watch, watchEffect, onMounted, ref, onBeforeUnmount } from "vue";
import tmText from "../tm-text/tm-text.vue";
const emits = defineEmits(["mountedCallback", "callback"]);
const props = defineProps({
  //是否跟随全局主题的变换而变换
  followTheme: {
    type: [Boolean, String],
    default: true,
  },
  fontSize: {
    type: Number,
    default: 30,
  },
  color: {
    type: String,
    default: "",
  },
  /**
   * @description 起始值
   */
  startVal: {
    type: Number,
    required: false,
    default: 0,
  },
  /**
   * @description 最终值
   */
  endVal: {
    type: Number,
    required: false,
    default: 2021,
  },
  /**
   * @description 从起始值到结束值数字变动的时间
   */
  duration: {
    type: Number,
    required: false,
    default: 3000,
  },
  /**
   * @description 是否自动播放
   */
  autoplay: {
    type: Boolean,
    required: false,
    default: true,
  },
  /**
   * @description 保留的小数位数
   */
  decimals: {
    type: Number,
    required: false,
    default: 0,
    validator(value) {
      return value >= 0;
    },
  },
  decimal: {
    type: String,
    required: false,
    default: ".",
  },
  /**
   * @description 三位三位的隔开效果
   */
  separator: {
    type: String,
    required: false,
    default: ",",
  },
  /**
   * @description 前缀
   * @example '¥' 人民币前缀
   */
  prefix: {
    type: String,
    required: false,
    default: "",
  },
  /**
   * @description 后缀
   * @example
   */
  suffix: {
    type: String,
    required: false,
    default: "",
  },
  /**
   * @description 是否具有连贯性
   */
  useEasing: {
    type: Boolean,
    required: false,
    default: true,
  },

  /**
   * @description 是否隔一段时间数字跳动，这里的跳动是隔一段时间设置初始值
   */
  isFrequent: {
    type: Boolean,
    required: false,
    default: false,
  },
  /**
   * @description 跳动间隔时间
   */
  frequentTime: {
    type: Number,
    required: false,
    default: 5000,
  },
});
let localStartVal = props.startVal;
let displayValue = ref(formatNumber(props.startVal));
let printVal = null;
let paused = false;
let localDuration = props.duration;
let startTime = null;
let timestamp = null;
let remaining = null;
let rAF = null;
let timer = null;
const countDown = props.startVal > props.endVal ? true : false;
watch([() => props.startVal, () => props.endVal], () => {
  start();
});
onMounted(() => {
  if (props.autoplay) {
    start();
  }
  if (props.isFrequent && props.frequentTime) {
    timer = setInterval(() => {
      start(randomNum(0, props.endVal));
    }, props.frequentTime);
  }
  emits("mountedCallback");
});

onBeforeUnmount(() => clearInterval(timer));

function easingFn(t = 0, b = 0, c = 0, d = 0) {
  let p = (c * (-Math.pow(2, (-10 * t) / d) + 1) * 1024) / 1023 + b;
  return p;
}
function randomNum(a, b) {
  return Math.round(Math.random() * (b - a) + a);
}
function start(startVal) {
  localStartVal = startVal || props.startVal;
  startTime = null;
  localDuration = props.duration;
  paused = false;

  rAF = requestAnimationFrame(count);
}
function pauseResume() {
  if (paused) {
    resume();
    paused = false;
  } else {
    pause();
    paused = true;
  }
}
function pause() {
  cancelAnimationFrame(rAF);
}
function resume() {
  startTime = null;
  localDuration = +remaining;
  localStartVal = +printVal;
  requestAnimationFrame(count);
}
function reset() {
  startTime = null;
  cancelAnimationFrame(rAF);
  displayValue.val = formatNumber(props.startVal);
}
function count(timestamp_e) {
  if (!startTime) startTime = timestamp_e;
  timestamp = timestamp_e;
  const progress = timestamp - startTime;
  remaining = localDuration - progress;

  if (props.useEasing) {
    if (countDown) {
      printVal =
        localStartVal -
          easingFn(progress, 0, localStartVal - props.endVal, localDuration) || 0;
    } else {
      printVal = easingFn(
        progress,
        localStartVal,
        props.endVal - localStartVal,
        localDuration
      );
    }
  } else {
    if (countDown) {
      printVal =
        localStartVal - (localStartVal - props.endVal) * (progress / localDuration);
    } else {
      printVal =
        localStartVal + (props.endVal - localStartVal) * (progress / localDuration);
    }
  }
  if (countDown) {
    printVal = printVal < props.endVal ? props.endVal : printVal;
  } else {
    printVal = printVal > props.endVal ? props.endVal : printVal;
  }

  displayValue.value = formatNumber(printVal);
  if (progress < localDuration) {
    rAF = requestAnimationFrame(count);
  } else {
    emits("callback");
  }
}
function isNumber(val) {
  return !isNaN(parseFloat(val));
}
function formatNumber(num) {
  num = num.toFixed(props.decimals);
  num += "";
  const x = num.split(".");
  let x1 = x[0];
  const x2 = x.length > 1 ? props.decimal + x[1] : "";
  const rgx = /(\d+)(\d{3})/;
  if (props.separator && !isNumber(props.separator)) {
    while (rgx.test(x1)) {
      x1 = x1.replace(rgx, "$1" + props.separator + "$2");
    }
  }
  return x1 + x2;
}

// 对外暴露的方法。
defineExpose({ start: start, reset: reset, pause: pause });
</script>

<style></style>
